/**
 *  ClassCompiler.java : IceBird project
 *  20:05:15 
 */
package icebird.compiler.ncomp;

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;

import icebird.compiler.bytecode.ByteCode;
import icebird.compiler.bytecode.BytecodeParser;
import icebird.metadata.Class;
import icebird.metadata.Method;

/**
 * @author Sergey Shulepoff[Knott]
 */
public class ClassCompiler {
	
	private static boolean verbose = true;
	private static String OUTPUT_DIR = "obj";
	
	private Class source;
	
	private static ArrayList<Class> compiled = new ArrayList<Class>();
	
	/**
	 * Create class compiler.
	 * @param src
	 */
	public ClassCompiler(Class src) {
		source = src;
	}
	
	public static void setOutputDir(String dir) {
		OUTPUT_DIR = dir;
	}
	
	public static void setVerbose(boolean nv) {
		verbose = nv;
	}
	
	/**
	 * Compile class.
	 * @return void
	 */
	public void compile() {
		String ofn = OUTPUT_DIR + "/" + source.getMangledName() + ".asm";
		
		if (verbose) {
			System.out.println("Compiling class: " + source.getName());
			System.out.println("Output: " + ofn);
		}
		
		Method[] methodTable = source.getMethodTable();
		FileOutputStream out = null;
		
		ArrayList<Label> externs = new ArrayList<Label>();
		ArrayList<Label> defines = new ArrayList<Label>();
		
		try {
			out = new FileOutputStream(ofn);
		} catch (Exception e) {}
		
		emitHeader(out);
		
		// For each method.
		for (Method method : methodTable) {
			System.out.println("Compiling method: " + method.getFullName());
			
			if (method.isNative()) {
				System.out.println("Method is native, Just binding.");
				continue;
			}
			
			CodeCompiler compiler = new CodeCompiler();
			BytecodeParser p = new BytecodeParser(
				new ByteCode(method), compiler
			);
			
			p.parse();
			
			try {
				compiler.getAssembler().emit(out);
			} catch (Exception e) {
				// Something goes wrong.
				System.out.println("Failed: " + e.getMessage());
			}
			
			externs = merge(externs, compiler.getExterns());
			defines = merge(defines, compiler.getAssembler().defines());
		}
		
		emitExterns(strip(defines, externs), out);
		
		if (!source.getName().equals("java/lang/Object")) {
			System.out.println("Compiling super class: " + source.getSuperName());
			
			ClassCompiler cc = new ClassCompiler(source.getSuperClass());
			cc.compile();
		}
		
		compiled.add(source);
	}
	
	public static void trail() {
		System.out.println("Compiling RTT");
		
		RTTBuilder.compileRTT(compiled);
	}
	
	private void emitHeader(OutputStream out) {
		// Emit header.
		X86Assembler asm = new X86TextAssembler();
		
		asm.header();
		asm.emit(out);
	}
	
	/**
	 * Emit externs.
	 * @return void
	 */
	private void emitExterns(ArrayList<Label> externs, OutputStream out) {
		// Emit externs.
		X86Assembler asm = new X86TextAssembler();
		for (Label extern : externs) {
			asm.extrn(extern);
		}
		
		asm.emit(out);
	}
	
	private static ArrayList<Label> strip(ArrayList<Label> defines, ArrayList<Label> externs) {
		ArrayList<Label> merged = new ArrayList<Label>();
		
		for (Label extern : externs) {
			if (!isExists(defines, extern)) {
				merged.add(extern);
			}
		}
		
		return merged;
	}
	
	// Merge to externs
	private static ArrayList<Label> merge(ArrayList<Label> old, ArrayList<Label> ext) {
		ArrayList<Label> merged = new ArrayList<Label>();
		
		for (Label l : old) {
			if (!isExists(ext, l)) { 
				merged.add(l);
			}
		}
		
		for (Label l : ext) {
			if (!isExists(old, l)) { 
				merged.add(l);
			}
		}
		
		return merged;
	}
	
	private static boolean isExists(ArrayList<Label> ext, Label l) {
		for (Label nl : ext) {
			if (nl.equals(l)) return true;
		}
		
		return false;
	}
}
